from bisect import insort_right, bisect_right

from utils.call import curry
from utils.copy import smartcopy, deepcopy_into
from utils.misc import MAXFLOAT


class Window(object):
    """A satisfiability window for an activity. This represents the time interval during which an 
    activity must be completed. All activities have one hard start and one hard end contraint (the 
    hard end constraint can be relaxed by using a very large value, e.g. INF or MAXFLOAT). The 
    window size is defined as the difference between the hard end and start dates.
    Additionally, a window may have an arbitrary number of soft end constraints, each with an 
    associated (linear) penalty weight."""
    def __init__(self, start, hard_end=MAXFLOAT, soft_ends=()):
        self.start = start
        self.hard_end = hard_end
        self.soft_ends = []
        for date, penalty in soft_ends:
            self.add_soft_end(date, penalty)
            
    deepcopy = deepcopy_into
    __deepcopy__ = curry(smartcopy,
                         shallowcopy_attrs=("start", "hard_end"), 
                         deepcopy_attrs=("soft_ends",))
    
    def __repr__(self):
        parts = ["%s{%s" % (type(self).__name__, self.start)]
        parts.extend(", %s(?%s)" % (date, penalty) for date, penalty in self.soft_ends)
        parts.append(", !%s}" % self.hard_end)
        return "".join(parts)
        
    @property
    def size(self):
        return self.hard_end - self.start
        
    def add_soft_end(self, date, penalty=1.0):
        insort_right(self.soft_ends, (float(date), float(penalty)))
        
    def penalty(self, end_date):
        """Compute the penalty from soft end constraints that results from ending the associated 
        activity at 'end_date'."""
        if end_date > self.hard_end:
            raise ValueError("argument end date violates the window's hard end constraint")
        total_penalty = 0.0
        for date, penalty in self.soft_ends:
            if date >= end_date:
                break
            total_penalty += penalty * (end_date - date)
        return total_penalty
        
        
